package com.lh0811.forge.pserv.uacs.server.component.data_scope.plugins;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.google.common.collect.Lists;
import com.lh0811.forge.dependency.basic_web.exception.CommonException;
import com.lh0811.forge.dependency.basic_web.response.ServerResponse;
import com.lh0811.forge.dependency.security.client.component.SecurityUtil;
import com.lh0811.forge.dependency.security.core.model.entity.SecurityDataRule;
import com.lh0811.forge.dependency.security.core.model.entity.SecurityPost;
import com.lh0811.forge.pserv.uacs.api.component.data_scope.DataScope;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.annotation.handler.DataScopeThreadLocalHandler;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.constant.RuleLogicEnum;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.constant.ValueStructEnum;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.constant.ValueTypeEnum;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.dto.TableNameDto;
import com.lh0811.forge.pserv.uacs.server.component.data_scope.provider.AbstractSysRuleValueProvider;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class DataScopeInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {


    private Map<String, AbstractSysRuleValueProvider> providerHashMap;

    public DataScopeInnerInterceptor(Map<String, AbstractSysRuleValueProvider> providerHashMap) {
        this.providerHashMap = providerHashMap;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    }

    /**
     * 该方法由JsqlParserSupport提供,主要用于通过API的方式操作SQL
     * 思路:通过API构建出新的条件,并将新的条件和之前的条件拼接在一起
     */
    @SneakyThrows
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        // 跳过没有注解的查询
        // 获取是否有DataScope注解
        DataScope dataScope = DataScopeThreadLocalHandler.getDataScope();
        if (ObjectUtils.isEmpty(dataScope)) return;
        // 获取 SQL 语句
        String originalSql = select.getPlainSelect().toString();
        // 判断是否存在子查询
        if (countSelectIgnoreCase(originalSql, "select") > 1) {
            throw CommonException.create(ServerResponse.createByError("动态数据权限暂时不支持带有子查询的SQL"));
        }
        log.info("即将执行的 SELECT SQL 语句是: " + originalSql);

        // 获取涉及到的表
        List<TableNameDto> tableNameList = getTableNameList(select.getPlainSelect());

        // 获取当前登录用户
        List<SecurityDataRule> dataRuleList = Lists.newArrayList();
        for (SecurityPost securityPost : SecurityUtil.getSecurityAuthority().getSecurityUser().getCurrTenantInfo().getPostList()) {
            for (SecurityDataRule securityDataRule : securityPost.getDataScopeList()) {
                dataRuleList.add(securityDataRule);
            }
        }
        // 没有数据规则
        if (CollectionUtils.isEmpty(dataRuleList)) return;

        // 筛选生效的数据规则
        List<String> tableNameStrList = tableNameList.stream().map(ele -> ele.getTableName()).collect(Collectors.toList());

        // 获取生效的数据规则
        List<SecurityDataRule> usableDataRuleList = dataRuleList.stream().filter(ele -> tableNameStrList.contains(ele.getDataTable())).collect(Collectors.toList());

        // 没有数据规则
        if (CollectionUtils.isEmpty(usableDataRuleList)) return;

        // 获取查询条件
        List<RuleValueDto> ruleValueDtoList = Lists.newArrayList();
        for (SecurityDataRule securityDataRule : usableDataRuleList) {
            ruleValueDtoList.add(getValue(securityDataRule));
        }

        // 获取计划执行的sql
        PlainSelect plainSelect = select.getPlainSelect();

        // 条件聚合
        Map<String, List<RuleValueDto>> groupByRule = ruleValueDtoList.stream().collect(Collectors.groupingBy(ele -> ele.getSecurityDataRule().getDataTable() + "-" + ele.getSecurityDataRule().getDataDimension() + "-" + ele.getSecurityDataRule().getRuleLogic()));
        for (Map.Entry<String, List<RuleValueDto>> listEntry : groupByRule.entrySet()) {
            String[] split = StringUtils.split(listEntry.getKey(), "-");
            String logic = split[2];
            if (StringUtils.equals(RuleLogicEnum.eq.getCode(), logic)) {
                ifEq(listEntry, plainSelect);
            } else if (StringUtils.equals(RuleLogicEnum.in_list.getCode(), logic)) {
                ifInList(listEntry, plainSelect);
            } else {
                throw CommonException.create(ServerResponse.createByError("暂时不支持规则逻辑:" + RuleLogicEnum.getByCode(logic)));
            }
        }
        log.info("修改后的sql: {}", plainSelect.toString());


    }

    private static void ifInList(Map.Entry<String, List<RuleValueDto>> listEntry, PlainSelect plainSelect) throws CommonException {
        // 只有一个规则
        if (listEntry.getValue().size() == 1) {
            // 获取规则和规则值
            RuleValueDto ruleValueDto = listEntry.getValue().get(0);
            if (CollectionUtils.isEmpty(ruleValueDto.getRuleVauleList())) {
                throw CommonException.create(ServerResponse.createByError("数据规则逻辑为在列表(in) 值列表不能为空"));
            }
            if (StringUtils.equals(ruleValueDto.getSecurityDataRule().getValueType(), ValueTypeEnum.str.getCode())) {
                List<String> ruleVauleList = ruleValueDto.getRuleVauleList();
                ExpressionList expressionList = new ExpressionList();
                for (String strValue : ruleVauleList) {
                    expressionList.add(new StringValue(strValue));
                }
                Parenthesis parenthesis = new Parenthesis(expressionList);
                InExpression inExpression = new InExpression();
                inExpression.setLeftExpression(new Column(ruleValueDto.getSecurityDataRule().getDataTable() + "." + ruleValueDto.getSecurityDataRule().getDataDimension()));
                inExpression.setRightExpression(parenthesis);
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else if (StringUtils.equals(ruleValueDto.getSecurityDataRule().getValueType(), ValueTypeEnum.num.getCode())) {
                List<Long> ruleVauleList = ruleValueDto.getRuleVauleList();
                ExpressionList expressionList = new ExpressionList();
                for (Long value : ruleVauleList) {
                    expressionList.add(new LongValue(value));
                }
                Parenthesis parenthesis = new Parenthesis(expressionList);
                InExpression inExpression = new InExpression();
                inExpression.setLeftExpression(new Column(ruleValueDto.getSecurityDataRule().getDataTable() + "." + ruleValueDto.getSecurityDataRule().getDataDimension()));
                inExpression.setRightExpression(parenthesis);
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else {
                throw CommonException.create(ServerResponse.createByError("暂时不支持出数字和字符串之外的值类型"));
            }
        }
        // 多个规则合并
        else {
            List<RuleValueDto> ruleValueList = listEntry.getValue();
            if (StringUtils.equals(ruleValueList.get(0).getSecurityDataRule().getValueType(), ValueTypeEnum.str.getCode())) {
                List<String> strValueList = Lists.newArrayList();
                for (RuleValueDto ruleValueDto : ruleValueList) {
                    strValueList.addAll(ruleValueDto.getRuleVauleList());
                }
                if (!CollectionUtils.isEmpty(strValueList)) {
                    strValueList = strValueList.stream().distinct().collect(Collectors.toList());
                }
                ExpressionList expressionList = new ExpressionList();
                for (String strValue : strValueList) {
                    expressionList.add(new StringValue(strValue));
                }
                Parenthesis parenthesis = new Parenthesis(expressionList);
                InExpression inExpression = new InExpression();
                inExpression.setLeftExpression(new Column(ruleValueList.get(0).getSecurityDataRule().getDataDimension()));
                inExpression.setRightExpression(parenthesis);
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else if (StringUtils.equals(ruleValueList.get(0).getSecurityDataRule().getValueType(), ValueTypeEnum.num.getCode())) {
                List<Long> numValueList = Lists.newArrayList();
                for (RuleValueDto ruleValueDto : ruleValueList) {
                    numValueList.addAll(ruleValueDto.getRuleVauleList());
                }
                if (!CollectionUtils.isEmpty(numValueList)) {
                    numValueList = numValueList.stream().distinct().collect(Collectors.toList());
                }
                ExpressionList expressionList = new ExpressionList();
                for (Long longValue : numValueList) {
                    expressionList.add(new LongValue(longValue));
                }
                Parenthesis parenthesis = new Parenthesis(expressionList);
                InExpression inExpression = new InExpression();
                inExpression.setLeftExpression(new Column(ruleValueList.get(0).getSecurityDataRule().getDataDimension()));
                inExpression.setRightExpression(parenthesis);

                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else {
                throw CommonException.create(ServerResponse.createByError("暂时不支持出数字和字符串之外的值类型"));
            }
        }
    }

    // 如果数据维度的判断逻辑是eq
    private static void ifEq(Map.Entry<String, List<RuleValueDto>> listEntry, PlainSelect plainSelect) throws CommonException {
        // 只有一个规则
        if (listEntry.getValue().size() == 1) {
            // 获取规则和规则值
            RuleValueDto ruleValueDto = listEntry.getValue().get(0);
            if (StringUtils.equals(ruleValueDto.getSecurityDataRule().getValueType(), ValueTypeEnum.str.getCode())) {
                EqualsTo equalsTo = new EqualsTo(new Column(ruleValueDto.getSecurityDataRule().getDataTable() + "." + ruleValueDto.getSecurityDataRule().getDataDimension()), new StringValue((String) ruleValueDto.getRuleValue()));
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), equalsTo);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(equalsTo);
                }
            } else if (StringUtils.equals(ruleValueDto.getSecurityDataRule().getValueType(), ValueTypeEnum.num.getCode())) {
                EqualsTo equalsTo = new EqualsTo(new Column(ruleValueDto.getSecurityDataRule().getDataTable() + "." + ruleValueDto.getSecurityDataRule().getDataDimension()), new LongValue((Long) ruleValueDto.getRuleValue()));
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), equalsTo);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(equalsTo);
                }
            } else {
                throw CommonException.create(ServerResponse.createByError("暂时不支持出数字和字符串之外的值类型"));
            }
        }
        // 多个规则的合并
        else {
            List<RuleValueDto> ruleValueList = listEntry.getValue();
            if (StringUtils.equals(ruleValueList.get(0).getSecurityDataRule().getValueType(), ValueTypeEnum.str.getCode())) {
                List<String> strValueList = ruleValueList.stream().map(ele -> (String) ele.getRuleValue()).collect(Collectors.toList());

                ExpressionList expressionList = new ExpressionList();
                for (String strValue : strValueList) {
                    expressionList.add(new StringValue(strValue));
                }
                Parenthesis parenthesis = new Parenthesis(expressionList);

                InExpression inExpression = new InExpression();
                inExpression.setLeftExpression(new Column(ruleValueList.get(0).getSecurityDataRule().getDataDimension()));
                inExpression.setRightExpression(parenthesis);

                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else if (StringUtils.equals(ruleValueList.get(0).getSecurityDataRule().getValueType(), ValueTypeEnum.num.getCode())) {
                List<Long> numValueList = ruleValueList.stream().map(ele -> (Long) ele.getRuleValue()).collect(Collectors.toList());
                ExpressionList expressionList = new ExpressionList(numValueList.stream().map(ele -> new LongValue(ele)).collect(Collectors.toList()));
                InExpression inExpression = new InExpression(new Column(ruleValueList.get(0).getSecurityDataRule().getDataDimension()), expressionList);
                if (ObjectUtils.isNotEmpty(plainSelect.getWhere())) {
                    AndExpression andExpression = new AndExpression(plainSelect.getWhere(), inExpression);
                    plainSelect.setWhere(andExpression);
                } else {
                    plainSelect.setWhere(inExpression);
                }
            } else {
                throw CommonException.create(ServerResponse.createByError("暂时不支持出数字和字符串之外的值类型"));
            }
        }
    }

    private static int countSelectIgnoreCase(String string, String targetStr) {
        // 使用正则表达式，忽略大小写匹配"select"
        Pattern pattern = Pattern.compile("(?i)" + targetStr);
        Matcher matcher = pattern.matcher(string);

        int count = 0;
        while (matcher.find()) {
            count++;
        }

        return count;
    }

    private static List<TableNameDto> getTableNameList(PlainSelect select) throws Exception {
        // 解析SQL语句
        List<TableNameDto> tableNameDtoList = Lists.newArrayList();
        log.info(select.toString());
        tableNameDtoList.add(getTableNameDtoByFromItemStr(select.getFromItem().toString()));
        if (!CollectionUtils.isEmpty(select.getJoins())) {
            for (Join join : select.getJoins()) {
                tableNameDtoList.add(getTableNameDtoByFromItemStr(join.getFromItem().toString()));
            }
        }
        return tableNameDtoList;
    }

    private static TableNameDto getTableNameDtoByFromItemStr(String fromItemStr) {
        if (StringUtils.containsIgnoreCase(fromItemStr, "as")) {
            fromItemStr = StringUtils.replaceIgnoreCase(fromItemStr, "as ", "");
        }
        String[] split = StringUtils.split(fromItemStr, " ");
        if (split.length == 1) {
            return new TableNameDto(split[0], null);
        } else {
            return new TableNameDto(split[0], split[1]);
        }
    }

    private RuleValueDto getValue(SecurityDataRule dataRuleVo) throws Exception {
        // 1-后台维护 2-系统数据源 ，这种都是后台直接填写值，或者从数据源中选择值填写到dataRuleVo.getRuleValue() 所以直接去 dataRuleVo.getRuleValue()就行
        if (StringUtils.equalsAny(dataRuleVo.getRuleValueSource(), "1", "2")) {
            return new RuleValueDto(dataRuleVo, Boolean.FALSE, dataRuleVo.getRuleValue(), Lists.newArrayList(StringUtils.split(dataRuleVo.getRuleValue(), ",")));
        }
        String ruleValueProvider = dataRuleVo.getRuleValueProvider();
        if (StringUtils.equalsAny(dataRuleVo.getValueStruct(), ValueStructEnum.SingleValue.getCode(), ValueStructEnum.Dict.getCode())) {
            AbstractSysRuleValueProvider sysRuleValueProvider = providerHashMap.get(ruleValueProvider);
            return new RuleValueDto(dataRuleVo, Boolean.FALSE, sysRuleValueProvider.adapterPageHelper(), null);
        } else {
            AbstractSysRuleValueProvider<List> sysRuleValueProvider = providerHashMap.get(ruleValueProvider);
            return new RuleValueDto(dataRuleVo, Boolean.FALSE, null, sysRuleValueProvider.adapterPageHelper());
        }
    }


    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    private static class RuleValueDto<T> {
        @Schema(description = "数据规则")
        private SecurityDataRule securityDataRule;
        @Schema(description = "是否是列表")
        private Boolean listFlag;
        @Schema(description = "值")
        private T ruleValue;
        @Schema(description = "值")
        private List<T> ruleVauleList;
    }
}
